<?php
/**
* xml-rpc server
* @author J.Ducastel <fredbird@fredbird.org>
* @version
* @uses domxml
*/
class ooXmlRpcServer {
/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
	/**
	*
	* @access
	*/
	function ooXmlRpcServer($iniFile=null) {
		$this->__construct($iniFile);
	}/**/

	/**
	*
	* @access
	*/
	function __construct($iniFile=null) {
		//$this->deb=&wak::getservice('wakdebug');
		$this->loadConfig($iniFile);
	}/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
	/**
	* Tries to serve current http xml-rpc method call
	* @access public
	* @return bool
	*/
	function serveRequest() {
		//$this=&domXmlRpcServer::_getsingleton();
		// check for http authentication
		if (!$this->authenticationCheck($this->cfg['users'])) {
			// return $this->raiseFa
			return false;
		}
		// retrieve and build method call
		if (!$call=&$this->getMethodCall()) {
			// failed, fault have been raised by getMethodCall
			// echo "invalid call : ".$this->deb->vardump($call);
			return false;
		}
		// runs call
		// echo $this->deb->vardump($this);
		if (!$response=&$this->ResponseToCall($call)) {
			// failed, raise fault
			return false;
		}
		// echo $deb->vardump($cm);
		// sending response
		return $this->sendResponse($response);
	}/**/

	/**
	* build and send methodResponse including fault
	* @param int $code faultCode
	* @param string $string faultString
	* @access public
	* @return bool
	*/
	function raiseFault($code,$string=null) {
		// has response already been sent ?
		if ($this->responseSent)
			return false;
		// getting predefined desc if required and available
		if (is_null($string) and isset($this->cfg['faults'][$code]))
			$string=$this->cfg['faults'][$code];
		$fault=&new ooXmlRpcFault($code,$string);
		if (!$fault->isValid()) {
			return false;
		}
		$response=&new ooXmlRpcMethodResponse();
		//$responseNode=&$response->getNode();
		//$fault->appendToNode($responseNode);
		$response->setContent($fault);
		return $this->sendResponse($response);
	}/**/

	/**
	*
	* @param
	* @access public
	* @return
	* /
	function () {

	}/**/
/*------------------------------------------------------------------------------
                                                                      properties
------------------------------------------------------------------------------*/
	/**
	* @var array configuration data
	* 'service_classes'=> array('serviceName'=>'class')
	* 'service_files'=> array('serviceName'=>'file')
	*/
	var $cfg=array(
		// http authentication config
		'auth'=>array(
			'use'=>false
			, 'pwd_file'=>'')
		, 'users'=>array()
		// classes associated to services
		, 'service_classes'=>array()
		// files defining service classes
		, 'service_files'=>array()
		// default faultstring for fault codes
		, 'faults'=>array(
			400=>'bad request'
			, 401=>'unauthorized'
			, 403=>'forbidden'
			, 404=>'not found'
			, 500=>'internal server error'
			, 501=>'not implemented'
			, 503=>'service unavailable'));
	/**
	* @var bool has methodResponse be sent ?
	*/
	var $responseSent=false;

	/**
	* @var type desc
	*/
/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
	/**
	* build response to call
	* find and execute call's implementation
	* @param ooxmlrpcmethodcall &$call
	* @access protected
	* @return ooxmlrpcmethodresponse
	*/
	function & responseToCall(&$call) {
		// $deb=&wak::getservice('wakdebug');
		$response=false;
		if (!is_a($call,'ooxmlrpcmethodcall')) {
			// bad argument
			$this->raiseFault(500);
			return $response;
		}
		$method=$call->getmethod(); //echo "<p>method: $method</p>";
		$service=$call->getServiceName(); //echo "<p>service: $service</p>";
		// get params
		$params=&$call->getParams();
		// build response
		$response=&new ooXmlRpcMethodResponse();
		// is call service based ?
		if ($service) {
			// include service file
			if (isset($this->cfg['service_files'][$service])) {
				$files=explode(' ',$this->cfg['service_files'][$service]);
				foreach ($files as $file) {
					if (!file_exists($file)) {
						// trigger_error("ooXmlRpcServer : required file $file don't exist",E_USER_WARNING);
						// return false;
					}
					include_once($file);
				}
			}
			// get service class
			if (isset($this->cfg['service_classes'][$service]))
				$class=$this->cfg['service_classes'][$service];
			else $class=$service;
			// is class defined ?
			if (!class_exists($class)) {
				// not defined, raising fault
				$str="ooXmlRpcServer : class $class undefined";
				$this->raiseFault(503,$str);
				return false;
			}
			// is method defined ?
			if (!in_array($method,get_class_methods($class))) {
				// method not implemented, raising fault
				$this->raiseFault(501);
				return false;
			}
			// run class method
			$result=eval('return '.$class.'::'.$method.'($params);');
			if (!$response->setContent($result)) {
				// invalid result, raise fault
				//echo '<p>invalid result : '.$deb->vardump($result).'</p>';
				$this->raiseFault('500','bad method result');
			}
			//$deb=&wak::getservice('wakdebug');
			//echo $deb->vardump($result);
			// echo wakaffvar($response);
		} else {
			// searching within method_functions
			// searching within own methods
			// not found
			// method not implemented, raising fault
			$this->raiseFault(501);
			return false;
		}
		return $response;
	}/**/

	/**
	* retrieves and build method call from http raw post data
	* @param
	* @access protected
	* @return domXmlRpcMethodCall|false
	*/
	function & getMethodCall() {
		// echo "getting call !";
		//$this=&domXmlRpcServer::_getsingleton();
		$call=false;
		if (!$xml=&$this->getrawpost()) {
			// no data at all,  raising fault
			$this->raiseFault(400,'no xml data');
			return $call;//
		}
		// building call instance
		$call=& new ooXmlRpcMethodCall();
		if (!$call->setFromXml($xml)) {
			// failed to retrieve xml, raising fault
			$this->raiseFault(400,'invalid xml data');
			$call=false;
		}
		return $call;
	}/**/

	/**
	* returns raw post data
	* @param
	* @access protected
	* @return string|false
	*/
	function getRawPost() {
		if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])
		or !$raw=$GLOBALS['HTTP_RAW_POST_DATA']) {
			// hack to retrieve raw data
			$raw=file_get_contents('php://input');
		}
		return $raw;
	}/**/

	/**
	* sends method response to client
	* @param
	* @access protected
	* @return
	*/
	function sendResponse(&$response) {
		if ($this->responseSent)
			return false;
		$xml=$response->getxml();
		// sending headers
		header('Content-type: text/xml');
		// sending content
		//$deb=&wak::getservice('wakdebug');
		//echo $deb->vardump($xml,'html',5000);
		echo $xml;
		$this->responseSent=true;
		return true;
	}/**/

	/**
	* loads config from ini file, merge data into $this->cfg
	* search ini file at cfg/{serverclass}.cfg.ini
	* @param string $file
	* @access protected
	* @return bool
	*/
	function loadConfig($file=null) {
		if (is_null($file))
			$file='cfg/'.get_class($this).'.cfg.ini'; //echo $file;
		if (file_exists($file) and $cfg=parse_ini_file($file,true)) {
			// merging new config with default one
			$this->cfg=array_merge_recursive($this->cfg,$cfg);
			//trigger_error("loaded config from $file");
			return true;
		}
		return false;
	}/**/

	/**
	* checks for http authentication
	* @param array users user => pwd list of authorized users with pwd
	* @access protected
	* @return bool
	*/
	function authenticationCheck($users=array()) {
		// is authentication required ?
		if (!$this->cfg['auth']['use'])
			return true;
		// are login/pwd provided ?
		if (!isset($_SERVER['PHP_AUTH_USER'])
		or !isset($_SERVER['PHP_AUTH_PW'])) {
			$realm='realm';
			//header( 'WWW-Authenticate: Basic realm="'.$realm.'"' );
			header( 'WWW-Authenticate: Basic' );
			header( 'HTTP/1.0 401 Unauthorized' );
			//echo 'Authorization Required.';
			$this->raiseFault(401);
			return false;
			exit;
		} else {
			// checking for provided login/pwd
			$provided_login=$_SERVER['PHP_AUTH_USER'];
			$provided_pwd=$_SERVER['PHP_AUTH_PW'];
			// searching for user/pwd
			foreach ($users as $login => $pwd) {
				if ($login==$provided_login and $pwd==$provided_pwd)
					return true;
			}
			// invalid user/pwd
			$this->raiseFault(401);
			return false;
		}
		
		return false;
	}/**/
}
?>
